/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * main.cc
 * Copyright (C) Jeff Goodhue 2011 <jeff@JeffLinux>
 * 
 * Calculator is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Calculator is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <gtkmm.h>
#include <iostream>
#include "config.h"
#include "nullable-dbl.h" //my home made class, just an double and a boolean.
#include "graph-area.h" //Inherits the DrawingArea class from gtkmm.
#include <boost/lexical_cast.hpp> //wonderful api to convert a string to a different type and vise-versa
#ifdef ENABLE_NLS
#  include <libintl.h>
#endif

#define KP_Multiply 0xFFAA //I could not figure out how to include the GTK header for these constanats... so I just declared them myself.
#define KP_Add 0xFFAB
#define KP_Subtract 0xFFAD
#define KP_Decimal 0xFFAE
#define KP_Divide 0xFFAF
#define KP_0 0xFFB0
#define KP_1 0xFFB1
#define KP_2 0xFFB2
#define KP_3 0xFFB3
#define KP_4 0xFFB4
#define KP_5 0xFFB5
#define KP_6 0xFFB6
#define KP_7 0xFFB7
#define KP_8 0xFFB8
#define KP_9 0xFFB9

#define UI_FILE "src/Calculator.glade" //Source for the Calculator GUI
#define gUI_FILE "src/gCalculator.glade" //Source for the Graphing Calculator GUI

Gtk::Window *calc_win = 0, *gCalc_win = 0; //These widgets will be called outside
Gtk::Entry *calcText = 0, *gCalcText = 0; //int main

GraphArea *graphArea = 0; //This is also called outside the main function...

bool wipe = false; //if wipe is true then the next number entered will rewrite the textbox. Only used in the basic calculator
bool shouldKill = true; //If a window is hidden and this is true, the application ends.
NullableDbl firstDbl; //in order to store the first variable
char operation = '+'; //stores the opertation.

static void numPress(Glib::ustring str) { //This is called by any number button on the basic calculator.
	Glib::ustring calcString = calcText->get_text(); //gets the textbox
	calc_win->unset_focus(); //In order for the enter button to continue to press "Enter" instead of the last pushed button...
	if(calcString.compare("0") == 0 || wipe) { //If wipe is true then this will begin a new string for the textbox
		calcString = str;
		wipe = false;
	}
	else if(calcString.length() < 17){ //Max length of a double...
			calcString += str;
	}
	calcText->set_text(calcString);	//Saves the string to the textbox
}

static void solve() { //Will calculate the variables
	double secDbl = boost::lexical_cast<double>(calcText->get_text());
	Glib::ustring resultString = ""; 
	switch(operation) {
	case '+':
		resultString = boost::lexical_cast<std::string>(firstDbl.getDbl() + secDbl); //performs the operation...
		break;
	case '-':
		resultString = boost::lexical_cast<std::string>(firstDbl.getDbl() - secDbl);
		break;
	case '*':
		resultString = boost::lexical_cast<std::string>(firstDbl.getDbl() * secDbl);
		break;
	case '/':
		resultString = boost::lexical_cast<std::string>(firstDbl.getDbl() / secDbl);
		break;
	default: //This should NEVER occur... as the default operation is not blank but an add sign
		resultString = "ERROR";
		break;
	}
	calcText->set_text(resultString);
	wipe = true; //Since the answer was placed in the textbox, the string should restart on next number press.
}

static void opPress(char op) { //When an operator is pressed
	if(firstDbl.getNull()) { //if the first variable is nulled then save the textbox to the first var
		firstDbl.setDbl(boost::lexical_cast<double>(calcText->get_text()));
		firstDbl.setNull(false);
		operation = op; //saves the operation type for the equation.
		wipe = true;
	} else { //if the first variable DOES exist then solve and save answer as the new first variable.
		solve();
		firstDbl.setDbl(boost::lexical_cast<double>(calcText->get_text()));
		operation = op;
	}
}

static void butEnterPress() { //Called when ENTER is pressed, simply solves and refreshes the equation.
	if(!firstDbl.getNull())
	{
		solve();
		firstDbl.setDbl(0.0);
		firstDbl.setNull(true);
	}
}

static void butClearPress() { //Refreshes the equation and textbox.
	firstDbl.setDbl(0);
	firstDbl.setNull(true);
	calcText->set_text("0");
}

static void butDecPress() { //Only one decimal can be in the string at once...
	Glib::ustring calcString = calcText->get_text();
	if(wipe) {
		calcString = "0.";
		wipe = false;
	}
	else if(calcString.find(".") == Glib::ustring::npos){
		calcString += ".";
	}
	calcText->set_text(calcString);
}

bool keyPressed(GdkEventKey *event) { //This is for keyboard support on the basic calculator, there is non for the graphing calculator...
	if(!event)
		return false;
	switch(event->keyval) { //case events are ASCII codes for numbers 0-9.
	case 48:
	case 49:
	case 50:
	case 51:
	case 52:
	case 53:
	case 54:
	case 55:
	case 56:
	case 57:
			//subtract the index code to make this easier
			numPress(boost::lexical_cast<std::string>(event->keyval - 48));//Calls numPress like a button was pressed.
			return true;
	case KP_0: //The remaining cases give support for the keypad.
	case KP_1:
	case KP_2:
	case KP_3:
	case KP_4:
	case KP_5:
	case KP_6:
	case KP_7:
	case KP_8:
	case KP_9:
			//subtract the index code to make this easier
			numPress(boost::lexical_cast<std::string>(event->keyval - 65456));//Calls numPress like a button was pressed.
			return true;
	case KP_Add:
			opPress('+');
			return true;
	case KP_Multiply:
			opPress('*');
			return true;
	case KP_Subtract:
			opPress('-');
			return true;
	case KP_Divide:
			opPress('/');
			return true;
	case KP_Decimal:
			butDecPress();
			return true;
	}
	return false;
}

static void switchWin(Glib::ustring str) { //Called when the window should switch and not end the program.
	shouldKill = false; //The program should not close when a window is hidden.
	if(str.compare("calc") == 0) { //Used a string so I knew what it ment.
		gCalc_win->hide();
		calc_win->show();
	}
	else {
		calc_win->hide();
		gCalc_win->show();
	}
		
}

static void killApp() { //Called when HIDE is called from a window
	if(shouldKill) //Test to make sure the user did not want to change window.
		Gtk::Main::quit(); //Ends the main thread, closing the program.
	shouldKill = true;
}

//GRAPHING CALCULATOR FUNCTIONS

static void gnumPress(Glib::ustring str) { //Called when a number is pressed on the graphing calculator.
	Glib::ustring calcString = gCalcText->get_text();
	gCalc_win->unset_focus();
	if(calcString.compare("0") == 0) {
		calcString = str;
	}
	else{ //The program WILL crash if a number is too long...
			calcString += str;
	}
	gCalcText->set_text(calcString);
}

static void gopPress(int i) { //Called when an operation or decimal is pressed on the graphing calculator.
	Glib::ustring calcString = gCalcText->get_text();
	gCalc_win->unset_focus();
	switch(i) {
	case 0: //There should be no reason to have more then one addition sign...
		if(calcString.find("+") == Glib::ustring::npos)
			calcString += "+";
		break;
	case 1: //As subtract and the decimal can be used more then once, this is exempt, the program WILL crash if used incorrectly.
		calcString += "-";
		break;
	case 2:
		calcString += ".";
		break;
	case 3: //There should be no reason to have more then one x variable.
		if(calcString.find("x") == Glib::ustring::npos)
			calcString += "x";
		break;
	}
	gCalcText->set_text(calcString);
}

static void gClear() { //Only refreshes the textbox, as there are no variables stored.
	gCalcText->set_text("");
}

//The format should be ####x (+ or -) ####, example: 5x+3

static void graph() { //oooh boy, this was a stinker, called when the "Graph" button was pressed.
	char str[30]; //I believe this is the max for the string, so I only need to allocate 30 spaces.
	strcpy(str, gCalcText->get_text().c_str()); //char array allows me to analyze the string.
	int length = gCalcText->get_text().length(); //As I need to know the actual length of the string in order to do this.
	double beginHeight = 0.0, endHeight = 0.0; //The coordinates for the graph.
	NullableDbl slope; //Saves the slope when built
	NullableDbl yIntercept; //Saves the intercept when built
	std::string numBuilder = ""; //Builds the numbers from the string.
	int charVal = 0; //So it doesn't get redeclared in each for loop.
	//bool buildCept = false //if it should build the y-intercept, turns out I don't need this...
    char op = '+'; //Default operation is addition.
	for(int i = 0; i < length; i++) { //Reads each character induvidually...
		charVal = (int)str[i]; //Stores the ASCII value of the character.
		if(charVal == 120) { //This is a lowercase x
			if(numBuilder.compare("") != 0 && numBuilder.compare("-") != 0) //if it is not blank or just negative
				slope.setDbl(boost::lexical_cast<double>(numBuilder)); //Saves the numBuilder to the slope value.
			else if(numBuilder.compare("-") == 0) //Allows for -x+5
				slope.setDbl(-1);
			else
				slope.setDbl(1); // This allows for x+5 instead of 1x+5. As lexical cast will believe its 0 instead...
			slope.setNull(false);
			numBuilder = "";
		}
		else if(charVal == 43 || charVal == 45) { //Char values for + and -
			if(slope.getNull()) { //This allows for the format #+-#x... Don't recommend it.
				if(numBuilder.compare("") != 0) {
					yIntercept.setDbl(boost::lexical_cast<double>(numBuilder));
					yIntercept.setNull(false);
					numBuilder = "";
				} else //This will SAVE the operation into the variable... so you can have -5x etc.
					numBuilder += str[i];
			}
			op = str[i]; //Sets the operation type. AHHH the char is called twice! the operation will get re-saved when it is actually used.
		}
		else if((charVal >= 48 && charVal <= 57) || charVal == 46) //If it is a number or a decimal, it will add to the numbuilder, 5..6x+3 WILL cause a crash.
			numBuilder += str[i];
	}
	if(yIntercept.getNull()) { //Will be true if the user followed the recommended format.
		if(numBuilder.compare("") != 0) //If there is a number leftover then store it into the y-intercept.
			yIntercept.setDbl(boost::lexical_cast<double>(numBuilder));
		else
			yIntercept.setDbl(0.0); //If not then the y-intercept is presumably 0.
		yIntercept.setNull(false);
	}
	//Theoretically, I should have parsed the slope, intercept, and operation from the textbox...
	//A statndard graph goes from bottom to top, programming-wise its top to bottom, so that needs to be inverted
	if(op == '+') {
		beginHeight = ((yIntercept.getDbl() * 14) * -1) + 140;
		endHeight = ((yIntercept.getDbl() * 14) * -1) + 140;
		beginHeight += slope.getDbl() * 140; //140 is the center of the graph.
		endHeight -= slope.getDbl() * 140;		
	} else if(op == '-') {
		beginHeight = (yIntercept.getDbl() * 14) + 140;
		endHeight = (yIntercept.getDbl() * 14) + 140;
		beginHeight += slope.getDbl() * 140;
		endHeight -= slope.getDbl() * 140;
	}
	graphArea->setCoords(0.0, beginHeight, 280.0, endHeight); //Sets the coordinates for the graph
	graphArea->refresh(); //calls the on_draw method.
}

//MAIN FUNCTION

int main (int argc, char *argv[])
{
	Gtk::Main kit(argc, argv);


	//Load the Glade file and instiate its widgets:
	Glib::RefPtr<Gtk::Builder> builder; //The builder is used to connect each widget from the glade file
	try
	{
		builder = Gtk::Builder::create_from_file(UI_FILE);
		builder->add_from_file(gUI_FILE); //the builder now has access to both glade files.
	}
	catch (const Glib::FileError & ex)
	{
		std::cerr << ex.what() << std::endl;
		return 1;
	}
	builder->get_widget("calcMain", calc_win); //connects the window to the widget
	builder->get_widget("gCalcWin", gCalc_win);
	if(gCalc_win) {
		builder->get_widget("gcalcText", gCalcText);
		builder->get_widget_derived("graphArea", graphArea); //Because my Graph class inherited the drawing area it needs to use this function instead.
		shouldKill = false; //I call the hide function at the end so I don't want the program to close.
		if(gCalcText && graphArea) {
			Gtk::Button *gbutCalc = 0, *gbutClear = 0, *gbutGraph = 0;
			Gtk::Button* numArr[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; //Declares and array for all number buttons
			Gtk::Button* opArr[] = {0, 0, 0, 0}; //BUTTON 0 = add, 1 = sub, 2 = dec, 3 = x
			for(int i = 0; i < 10; i++) { //for loop in order to bind all the buttons in the array at once.
				builder->get_widget("gbut" + boost::lexical_cast<std::string>(i), numArr[i]);
				if(numArr[i])
					numArr[i]->signal_clicked().connect(sigc::bind<Glib::ustring>(sigc::ptr_fun(gnumPress), boost::lexical_cast<std::string>(i)));
			}
			for(int i = 0; i < 4; i++) { //for loop in order to bind all the buttons in the array at once.
				builder->get_widget("gbut" + boost::lexical_cast<std::string>(i + 10), opArr[i]);
				if(opArr[i])
					opArr[i]->signal_clicked().connect(sigc::bind<int>(sigc::ptr_fun(gopPress), i));
			}
			builder->get_widget("gbutCalculator", gbutCalc);
			builder->get_widget("gbutGraph", gbutGraph);
			builder->get_widget("gbutClear", gbutClear);
			if(gbutCalc) {
				gbutCalc->signal_clicked().connect(sigc::bind<Glib::ustring>(sigc::ptr_fun(switchWin), "calc"));
			}
			if(gbutGraph) {
				gbutGraph->signal_clicked().connect(sigc::ptr_fun(graph));
			}
			if(gbutClear) {
			 gbutClear->signal_clicked().connect(sigc::ptr_fun(gClear));
			}
		}
		gCalc_win->signal_hide().connect(sigc::ptr_fun(killApp)); //When HIDE is called, call killApp
		gCalc_win->hide(); //The basic Calulator should only be visible at first.
	}
	if (calc_win)
	{
		builder->get_widget("calcTxt", calcText);
		if(calcText) {
			Gtk::Button *butAdd = 0, *butSub = 0, *butMult = 0, *butDev = 0, *butDec = 0, *butEnter = 0, *butClear = 0, *butGraph = 0; //Decalres all non-number buttons
			Gtk::Button* numArr[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; //Declares and array for all number buttons
			for(int i = 0; i < 10; i++) { //for loop in order to bind all the buttons in the array at once.
				builder->get_widget("but" + boost::lexical_cast<std::string>(i), numArr[i]);
				if(numArr[i])
					numArr[i]->signal_clicked().connect(sigc::bind<Glib::ustring>(sigc::ptr_fun(numPress), boost::lexical_cast<std::string>(i)));
			}
			builder->get_widget("butAdd", butAdd);
			builder->get_widget("butSub", butSub);
			builder->get_widget("butMult", butMult);
			builder->get_widget("butDev", butDev);
			builder->get_widget("butDec", butDec);
			builder->get_widget("butEnter", butEnter);
			builder->get_widget("butClear", butClear);
			builder->get_widget("butGraph", butGraph);
			if(butAdd) {
				butAdd->signal_clicked().connect(sigc::bind<char>(sigc::ptr_fun(opPress), '+'));
			}
			if(butSub) {
				butSub->signal_clicked().connect(sigc::bind<char>(sigc::ptr_fun(opPress), '-'));
			}
			if(butMult) {
				butMult->signal_clicked().connect(sigc::bind<char>(sigc::ptr_fun(opPress), '*'));
			}
			if(butDev) {
				butDev->signal_clicked().connect(sigc::bind<char>(sigc::ptr_fun(opPress), '/'));
			}
			if(butDec) {
				butDec->signal_clicked().connect(sigc::ptr_fun(butDecPress));
			}
			if(butEnter) {
				butEnter->signal_clicked().connect(sigc::ptr_fun(butEnterPress));
			}
			if(butClear) {
				butClear->signal_clicked().connect(sigc::ptr_fun(butClearPress));
			}
			if(butGraph) {
				butGraph->signal_clicked().connect(sigc::bind<Glib::ustring>(sigc::ptr_fun(switchWin), "gCalc"));
			}
		}
		calc_win->signal_key_press_event().connect(sigc::ptr_fun(keyPressed), false);//connects event to entire window. For keypresses.
		calc_win->signal_hide().connect(sigc::ptr_fun(killApp));
		kit.run();
	}
	return 0;
}
